package com.hup.utils.swing.frame;

import com.hup.utils.swing.model.BaseWindowConfig;
import lombok.extern.log4j.Log4j2;
import sun.awt.windows.WToolkit;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 实现功能:
 * 1.窗口配置变化时,保存配置({@link BaseWindowConfig}):移动,缩放(一般缩放,最大化/还原),置顶
 * 2.事件传递:[移动,显示,关闭...(按需增加方法)]
 * @author hugan
 * @date 2019/12/10
 */
@Log4j2
public class WindowConfigBinder {

    /**
     * @param handler notNull
     */
    public static void bind(JFrame frame, FrameStateHandler handler) {
        StateListener listener = new StateListener(frame, handler);
        frame.addComponentListener(listener);
        frame.addWindowStateListener(listener);
        frame.addWindowListener(listener);
        frame.addPropertyChangeListener("alwaysOnTop", listener);
    }

    /**
     * <pre>
     * 实现功能:
     * 1.窗口配置变化时,保存配置({@link BaseWindowConfig}):移动,缩放(一般缩放,最大化/还原),置顶
     * 2.事件传递:[移动,显示,关闭...(按需增加方法)],传递前都是先处理完本类的功能(意思是,若另行添加监听,则新监听的回调会比Handler的早触发)
     * 3.定时器timer:[移动,缩放]n秒后才触发保存配置, 窗口隐藏(下文定义)后,马上销毁timer
     * 继承方法:
     * 1.1[componentMoved][componentResized,windowStateChanged][propertyChange]
     * 为实现[3]的[窗口隐藏]逻辑,根据下文的生命周期,重写方法
     * 3.1[windowClosed]后定为隐藏: 点关闭按钮,dispose()后触发
     * 3.2[componentHidden]后定为隐藏: setVisible(false)后触发
     * 3.3[componentShown]后定为显示: 在[setVisible(false),点关闭按钮,dispose()]后,到展示时都会触发
     *  componentHidden和windowClosed不会同时触发??(暂时没遇到)
     */
    private static class StateListener extends WindowAdapter implements ComponentListener, PropertyChangeListener {
        /**
         * 生命周期回调过程
         *  //从new到显示
         *  componentMoved[row:47]:
         *  componentMoved[row:47]:
         *  componentResized[row:52]:
         *  //setVisible(true);
         *  windowActivated[row:87]:
         *  componentResized[row:52]:
         *  componentMoved[row:47]:
         *  componentShown[row:37]:
         *  windowOpened[row:62]:(api注解:只有第一次显示才触发)
         *
         * 最小的触发
         *  //setVisible(true);
         *  windowActivated[row:108]:
         *  componentShown[row:58]:
         *  //setVisible(false);
         *  windowDeactivated[row:113]:
         *  componentHidden[row:63]:
         *
         * 关闭方式的区别
         *  //点关闭按钮
         *  windowClosing[row:67]:
         *  windowDeactivated[row:92]:
         *  windowClosed[row:72]:
         *  //dispose();
         *  windowDeactivated[row:120]:
         *  windowClosed[row:100]:
         *  //dispose->setVisible(true);
         *  componentMoved[row:75]:
         *  componentResized[row:80]:
         *  windowActivated[row:115]:
         *  componentResized[row:80]:
         *  componentMoved[row:75]:
         *  componentShown[row:65]:
         *
         *  //跳到其他窗口
         *  windowDeactivated[row:92]:
         *  //回到窗口
         *  windowActivated[row:87]:
         *
         *  结论:看类注解
         */
        private static final String THREAD_HEAD = "frameState_";

        /**
         * 每次窗口[移动,缩放]后,x秒后触发[保存配置]
         */
        private static final long DELAY = 1000;

        private JFrame frame;
        private String frameClz;
        private FrameStateHandler handler;

        /**
         * 用于移动或缩放后,延迟触发任务
         */
        private Timer timer;
        private TimerTask lastTimerTask;

        public StateListener(JFrame frame, FrameStateHandler handler) {
            this.frame = frame;
            frameClz = frame.getClass().getSimpleName();
            this.handler = handler;
        }

        //===生命周期起止
        @Override
        public void componentShown(ComponentEvent e) {
            //创建timer前都先检查destroy
            destroyTimer();
            timer = new Timer(THREAD_HEAD + frameClz + "_" + this.hashCode(), true);
            handler.frameShow();
        }

        @Override
        public void windowClosed(WindowEvent e) {
            destroyTimer();
            handler.frameClosedOrHide(true);
        }

        @Override
        public void componentHidden(ComponentEvent e) {
            destroyTimer();
            handler.frameClosedOrHide(false);
        }

        private void destroyTimer() {
            if (lastTimerTask != null) {
                lastTimerTask.cancel();
                lastTimerTask = null;
            }
            if (timer != null) {
                timer.cancel();
                timer = null;
            }
        }

        //===[位置,尺寸]
        @Override
        public void componentMoved(ComponentEvent e) {
            //log.info("");
            startTimerTask();
            handler.frameMoved();
        }

        @Override
        public void componentResized(ComponentEvent e) {
            //log.info("");
            startTimerTask();
        }

        private void startTimerTask() {
            //最小化后,再次打开时,会触发componentMoved,要靠isActive判断
            log.debug("timer!=null:{}, {}, {}", timer != null, frame.isActive(), frame.isShowing());
            boolean needRun = timer != null && frame.isActive();
            if (!needRun) return;

            //null时是窗口隐藏时,不处理
            if (lastTimerTask != null) {
                lastTimerTask.cancel();
            }
            timer.schedule(lastTimerTask = new TimerTask() {
                @Override
                public void run() {
                    timer.purge();//只要不是本线程阻塞,队列中就不会有任务,purge只是"保险"
                    saveLocSize();
                    handler.frameStopMoveDelay();
                }
            }, DELAY);
        }

        private void saveLocSize() {
            //log.info("");
            boolean isMax = frame.getExtendedState() == Frame.MAXIMIZED_BOTH;
            log.debug("最大化={}", isMax);
            if (!isMax) {
                //最大化时不要保存
                BaseWindowConfig config = handler.getConfig();
                config.setLoc(frame.getLocation());
                config.setSize(frame.getSize());
            }
            //窗口最大化后,会触发size改变,所以下文的最大化是没保存的,要在这里保存
            handler.saveConfig("locSize");
        }

        //===最大化/还原

        /**
         *
         * @apiNote {@link Toolkit#isFrameStateSupported(int)}, {@link WToolkit}中只支持[最大/最小化,一般]
         */
        @Override
        public void windowStateChanged(WindowEvent e) {
            int state = e.getNewState();
            if (Frame.MAXIMIZED_BOTH == state
                    || Frame.MAXIMIZED_HORIZ == state || Frame.MAXIMIZED_VERT == state//目前JDK不支持
                    || Frame.NORMAL == state) {
                //log.info("");
                BaseWindowConfig config = handler.getConfig();
                config.setWinState(state);
                //handler.saveConfig("windowState");//不触发保存,因为肯定会触发saveLocSize
                handler.frameStateChanged(state);
            }
        }

        //置顶
        @Override
        public void propertyChange(PropertyChangeEvent e) {
            BaseWindowConfig config = handler.getConfig();
            config.setAlwaysOnTop((Boolean) e.getNewValue());
            handler.saveConfig("onTop");
        }

        @Override
        public void windowClosing(WindowEvent e) {
            handler.frameClosing();
        }
    }

    public interface FrameStateHandler {
        BaseWindowConfig getConfig();

        void saveConfig(String desc);

        /**
         * 窗口显示后触发
         */
        default void frameShow() {
        }

        /**
         * 关闭前触发
         */
        default void frameClosing() {
        }

        /**
         * 窗口关闭后触发
         * @param isClose true-调用dispose或点击关闭按钮;false-调用setVisible(false)
         */
        default void frameClosedOrHide(boolean isClose) {
        }

        /**
         * 窗口坐标改变时都会触发
         */
        default void frameMoved() {
        }

        /**
         * 窗口最大化和还原时触发
         * @param state {@link Frame#NORMAL}, {@link Frame#MAXIMIZED_BOTH}
         * @apiNote 设置窗口状态时使用:{@link Frame#setExtendedState}
         */
        default void frameStateChanged(int state) {
        }

        /**
         * 窗口停止移动n秒后触发
         */
        default void frameStopMoveDelay() {
        }
    }

}
